package com.zhangh.summer.admin.support.lock;


import com.zhangh.summer.admin.support.ds.common.MixedDS;
import com.zhangh.summer.admin.support.ds.redis.RedisClient;
import com.zhangh.summer.admin.support.spring.util.SpringBeanUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

public class RedisLocker implements DistributedLocker {
    private static final Logger logger= LoggerFactory.getLogger(RedisLocker.class);


    private static RedisLocker singleton = null;
    private static RedisClient redisClient;


    private static final String OK = "OK";

    /** NX|XX, NX -- Only set the key if it does not already exist. XX -- Only set the key if it already exist. **/
    private static final String NOT_EXIST = "NX";
    private static final String EXIST = "XX";

    /** expx EX|PX, expire time units: EX = seconds; PX = milliseconds **/
    private static final String SECONDS = "EX";
    private static final String MILLISECONDS = "PX";

    private static final int DEFAULT_TIME_SEC=30;
    private static final int SLEEP_TIME_SEC=1;



    public static RedisLocker getInstance(){
        if(singleton == null){
            synchronized (RedisLocker.class){
                if(singleton == null){
                    singleton = new RedisLocker();
                }
            }
        }
        return singleton;
    }

    public RedisLocker(){
        redisClient= SpringBeanUtil.getBeanByType(MixedDS.class).redis();
    }


    @Override
    public void lock(String lockKey) {
        while (true) {
            String result = redisClient.set(lockKey, lockKey, NOT_EXIST, SECONDS, DEFAULT_TIME_SEC);//默认30s
            if (OK.equals(result)) {
                //开启定时刷新过期时间
                //isOpenExpirationRenewal = true;
                //scheduleExpirationRenewal();
                break;
            }
            sleepBySecond(SLEEP_TIME_SEC);
        }
    }

    @Override
    public void lock(String lockKey, int timeout) {
        while (true) {
            String result = redisClient.set(lockKey, lockKey, NOT_EXIST, MILLISECONDS, timeout);
            if (OK.equals(result)) {
                //开启定时刷新过期时间
                //isOpenExpirationRenewal = true;
                //scheduleExpirationRenewal();
                break;
            }
            sleepBySecond(SLEEP_TIME_SEC);
        }
    }

    @Override
    public void lock(String lockKey, TimeUnit unit, int timeout) {
        while (true) {
            String result = redisClient.set(lockKey, lockKey, NOT_EXIST, MILLISECONDS, unit.toMillis(timeout));
            if (OK.equals(result)) {
                //开启定时刷新过期时间
                //isOpenExpirationRenewal = true;
                //scheduleExpirationRenewal();
                break;
            }
            sleepBySecond(SLEEP_TIME_SEC);
        }
    }

    @Override
    public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
        int usedTime=0;
        while (true) {
            if (waitTime<=usedTime){
                //取锁等待超时，获取锁失败。
                logger.error("取锁等待超时，获取锁失败");
                break;
            }
            String result = redisClient.set(lockKey, lockKey, NOT_EXIST, MILLISECONDS, unit.toMillis(leaseTime));
            if (OK.equals(result)) {
                //开启定时刷新过期时间
                //isOpenExpirationRenewal = true;
                //scheduleExpirationRenewal();
                break;
            }
            sleepBySecond(SLEEP_TIME_SEC);
            usedTime+=SLEEP_TIME_SEC;
        }
        if (waitTime<=usedTime){
            return false;
        }
        return true;
    }

    @Override
    public void unlock(String lockKey) {
        String checkAndDelScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "return redis.call('del', KEYS[1]) " +
                "else " +
                "return 0 " +
                "end";
        Object obj=redisClient.eval(checkAndDelScript, 1, lockKey, lockKey);
        //isOpenExpirationRenewal = false;
    }




    private  void sleepBySecond(int sencond){
        try {
            Thread.sleep(sencond*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
